package com.wg.core.rank;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.wg.db.redis.Redis;
import redis.clients.jedis.Tuple;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 排行榜工具类
 * 支持分数更新、排名查询、批量操作等功能，包含参数校验、异常处理及性能优化
 *
 * @author 少爷123
 */
public class RankUtil {
    private static final Log log = LogFactory.get();

    /**
     * 添加或更新成员分数
     *
     * @param redis   Redis客户端实例（不能为null）
     * @param rankKey 排行榜键（不能为null/空）
     * @param score   分数值（支持正负）
     * @param member  成员标识（不能为null/空）
     * @throws IllegalArgumentException 参数非法时抛出
     * @throws RuntimeException         Redis操作失败时抛出
     */
    public static void addOrUpdateScore(Redis redis, String rankKey, long score, String member) {
        // 参数校验
        validateParams(redis, rankKey, member);

        try {
            redis.zadd(rankKey, score, member);
        } catch (Exception e) {
            log.error("添加/更新分数失败，rankKey={}, member={}, score={}", rankKey, member, score, e);
        }
    }

    /**
     * 获取排行榜成员总数
     *
     * @param redis   Redis客户端实例（不能为null）
     * @param rankKey 排行榜键（不能为null/空）
     * @return 成员数量（异常时返回0）
     */
    public static long getMemberCount(Redis redis, String rankKey) {
        if (!validateParams(redis, rankKey)) {
            return 0;
        }

        try {
            return redis.zcard(rankKey);
        } catch (Exception e) {
            log.error("获取成员数量失败，rankKey={}", rankKey, e);
            return 0;
        }
    }

    /**
     * 获取成员分数
     *
     * @param redis   Redis客户端实例（不能为null）
     * @param rankKey 排行榜键（不能为null/空）
     * @param member  成员标识（不能为null/空）
     * @return 分数值（不存在时返回默认值0）
     */
    public static long getScore(Redis redis, String rankKey, String member) {
        return getScore(redis, rankKey, member, 0L);
    }

    /**
     * 获取成员分数
     *
     * @param redis        Redis客户端实例（不能为null）
     * @param rankKey      排行榜键（不能为null/空）
     * @param member       成员标识（不能为null/空）
     * @param defaultValue 成员不存在时的默认值
     * @return 分数值（不存在时返回defaultValue）
     */
    public static long getScore(Redis redis, String rankKey, String member, long defaultValue) {
        if (!validateParams(redis, rankKey, member)) {
            return defaultValue;
        }

        try {
            Double score = redis.zscore(rankKey, member);
            return score != null ? Convert.toLong(score) : defaultValue;
        } catch (Exception e) {
            log.error("获取分数失败，rankKey={}, member={}", rankKey, member, e);
            return defaultValue;
        }
    }

    /**
     * 移除排行榜成员
     *
     * @param redis   Redis客户端实例（不能为null）
     * @param rankKey 排行榜键（不能为null/空）
     * @param member  成员标识（不能为null/空）
     * @throws IllegalArgumentException 参数非法时抛出
     * @throws RuntimeException         Redis操作失败时抛出
     */
    public static void removeMember(Redis redis, String rankKey, String member) {
        validateParams(redis, rankKey, member);

        try {
            redis.zrem(rankKey, member);
        } catch (Exception e) {
            log.error("移除成员失败，rankKey={}, member={}", rankKey, member, e);
            throw new RuntimeException("移除成员失败，请检查Redis连接或参数", e);
        }
    }

    /**
     * 获取成员排名
     *
     * @param redis    Redis客户端实例（不能为null）
     * @param rankKey  排行榜键（不能为null/空）
     * @param member   成员标识（不能为null/空）
     * @param isAsc    是否升序（true：升序-分数从小到大；false：降序-分数从大到小）
     * @param is1Based 是否1-based排名（true：排名从1开始；false：0-based）
     * @return 排名值（不存在时返回-1）
     */
    public static long getRank(Redis redis, String rankKey, String member, boolean isAsc, boolean is1Based) {
        if (!validateParams(redis, rankKey, member)) {
            return -1;
        }

        try {
            Long rank = isAsc ? redis.zrank(rankKey, member) : redis.zrevrank(rankKey, member);
            return rank != null ? (is1Based ? rank + 1 : rank) : -1;
        } catch (Exception e) {
            log.error("获取排名失败，rankKey={}, member={}, isAsc={}, is1Based={}", rankKey, member, isAsc, is1Based, e);
            return -1;
        }
    }

    /**
     * 快速判断排行榜是否存在
     *
     * @param redis   Redis客户端实例（不能为null）
     * @param rankKey 排行榜键（不能为null/空）
     * @return 存在返回true，否则false（异常时返回false）
     */
    public static boolean exists(Redis redis, String rankKey) {
        if (!validateParams(redis, rankKey)) {
            return false;
        }

        try {
            return redis.exists(rankKey);
        } catch (Exception e) {
            log.error("判断排行榜存在性失败，rankKey={}", rankKey, e);
            return false;
        }
    }

    /**
     * 获取指定排名区间的成员及分数
     *
     * @param redis   Redis客户端实例（不能为null）
     * @param rankKey 排行榜键（不能为null/空）
     * @param start   起始排名（闭区间，支持负数索引，如-1表示最后一个元素）
     * @param end     结束排名（闭区间）
     * @param isAsc   是否升序（true：升序；false：降序）
     * @return 成员及分数组合集合（无成员时返回空集合）
     */
    public static Set<Tuple> getRangeWithScores(Redis redis, String rankKey, long start, long end, boolean isAsc) {
        if (!validateParams(redis, rankKey)) {
            return new HashSet<>();
        }

        try {
            return isAsc ? redis.zrangeWithScores(rankKey, start, end)
                    : redis.zrevrangeWithScores(rankKey, start, end);
        } catch (Exception e) {
            log.error("获取区间成员失败，rankKey={}, start={}, end={}, isAsc={}", rankKey, start, end, isAsc, e);
            return new HashSet<>();
        }
    }

    /**
     * 批量添加/更新成员分数（使用管道优化，减少网络IO）
     *
     * @param redis        Redis客户端实例（不能为null）
     * @param rankKey      排行榜键（不能为null/空）
     * @param memberScores 成员-分数字典（不能为null/空）
     * @throws IllegalArgumentException 参数非法时抛出
     * @throws RuntimeException         Redis操作失败时抛出
     */
    public static void batchAddScores(Redis redis, String rankKey, Map<String, Long> memberScores) {
        if (redis == null) {
            throw new IllegalArgumentException("redis实例不能为null");
        }
        if (StrUtil.isBlank(rankKey)) {
            throw new IllegalArgumentException("rankKey不能为null或空字符串");
        }
        if (memberScores.isEmpty()) {
            log.warn("批量添加分数失败，memberScores为空");
            return;
        }

        Map<String, Double> scoreMap = new HashMap<>();
        for (Map.Entry<String, Long> entry : memberScores.entrySet()) {
            scoreMap.put(entry.getKey(), Convert.toDouble(entry.getValue()));
        }

        try {
            redis.zadd(rankKey, scoreMap);
        } catch (Exception e) {
            log.error("批量添加分数失败，rankKey={}, memberScores大小={}", rankKey, memberScores.size(), e);
        }
    }

    /**
     * 校验核心参数（redis、rankKey、member）
     *
     * @param redis   Redis实例
     * @param rankKey 排行榜键
     * @param member  成员标识（可选，允许null）
     * @return 参数合法返回true，否则false
     */
    private static boolean validateParams(Redis redis, String rankKey, String member) {
        if (redis == null) {
            log.warn("参数校验失败：redis实例不能为null");
            return false;
        }
        if (StrUtil.isBlank(rankKey)) {
            log.warn("参数校验失败：rankKey不能为null或空字符串");
            return false;
        }
        if (StrUtil.isBlank(member)) {
            log.warn("参数校验失败：member不能为null或空字符串");
            return false;
        }
        return true;
    }

    /**
     * 校验核心参数（redis、rankKey）
     *
     * @param redis   Redis实例
     * @param rankKey 排行榜键
     * @return 参数合法返回true，否则false
     */
    private static boolean validateParams(Redis redis, String rankKey) {
        if (redis == null) {
            log.warn("参数校验失败：redis实例不能为null");
            return false;
        }
        if (StrUtil.isBlank(rankKey)) {
            log.warn("参数校验失败：rankKey不能为null或空字符串");
            return false;
        }
        return true;
    }
}